
%%

%class Scanner
%unicode
%line
%column
%type Token

%{
  private Token tok(String tag) {
    return new Token(tag, yytext(), yyline+1, yycolumn+1);
  }

  private Token tok() {
    return tok(yytext());
  }

  private void err(String message) {
    throw new LexerException(message, yyline+1, yycolumn+1);
  }
%}

/* special characters */
LineTerminator  = \r|\n|\r\n
InputCharacter  = [^\r\n]
WhiteSpace      = {LineTerminator} | [ \t\f]
StringCharacter = [\x20\x21\x23-\x5b\x5d-\x7e]
EscapeCharacter = [\\\"tn]

/* comments */
Comment            = {TraditionalComment} | {EndOfLineComment}
TraditionalComment = "/*" ({LineTerminator}|[^\*]|(\*[^/]))*(\*)* "*/"
EndOfLineComment   = "//" {InputCharacter}* {LineTerminator}
MalformedComment   = "/*"

/* identifiers */
IdentifierCharacter    = [A-Z] | [a-z] | [0-9] | "_"
ClassIdentifier        = [A-Z] {IdentifierCharacter}*
Identifier             = [a-z] {IdentifierCharacter}*

/* literals */
IntegerLiteral = 0 | [1-9][0-9]*
StringLiteral  = "\"" ((\\{EscapeCharacter})|{StringCharacter})* "\""

/* language */
Keywords = class|extends|static|void|int|boolean|
           string|
           return|if|else|while|break|continue|this|new|
           length|
           true|false|null

%%

<YYINITIAL> {
  /* keywords */
  {Keywords}                     { return tok(); }

  /* identifiers */
  {ClassIdentifier}              { return tok("CLASS_ID"); }
  {Identifier}                   { return tok("ID"); }

  /* literals */
  {IntegerLiteral}               { return tok("INTEGER"); }
  {StringLiteral}                { return tok("STRING"); }
  "\""                           { err("malformed string literal"); }

  /* operators */
  [\[\]\(\)\.\-!\*\/\%\+\<\>\=]  { return tok(); }
  [\<\>\=!]\=                    { return tok(); }
  &&|\|\|                        { return tok(); }

  /* structure */
  [;,\{\}]                       { return tok(); }

  /* comments */
  {Comment}                      { /* ignore */ }
  {MalformedComment}             { err("unterminated comment"); }

  /* whitespace */
  {WhiteSpace}                   { /* ignore */ }
  
  "_"                            { err("an identifier cannot start with '_'"); }
}

 /* error fallback */
.|\n                             { err("invalid character '" + yytext() + "'"); }

